﻿#include "AiWebsocket.h"

#include <QDebug>
#include <QJsonDocument>
#include <QJsonArray>
#include <QMessageBox>
#include "singleton.h"

AiWebsocket::AiWebsocket(QObject* parent)
    : QObject(parent)
    , m_connected(false)
    , m_chn(0)
{
    this->init();

    connect(Singleton::Instance(), &Singleton::sigsendRequest, this, &AiWebsocket::sendRequest);

    connect(Singleton::Instance(), &Singleton::sigsendBinaryMessage, this, &AiWebsocket::sendBinaryMessage);

    qRegisterMetaType<DeviceInfo>("DeviceInfo");
    qRegisterMetaType<DeviceRunInfo>("DeviceRunInfo");
    qRegisterMetaType<NetworkParam>("NetworkParam");
    qRegisterMetaType<WsBinHeader>("WsBinHeader");
    qRegisterMetaType<AlarmExtern>("AlarmExtern");
    qRegisterMetaType<DetPeopleParam>("DetPeopleParam");
    qRegisterMetaType<CameraInfo>("CameraInfo");
    qRegisterMetaType<CameraParam>("CameraParam");
    qRegisterMetaType<DetBeltParam>("DetBeltParam");
    qRegisterMetaType<AppUpgrade>("AppUpgrade");
    qRegisterMetaType<CoalSpdParam>("CoalSpdParam");
    qRegisterMetaType<CoalSpdValue>("CoalSpdValue");
    qRegisterMetaType<OfflineSnapParam>("OfflineSnapParam");
    qRegisterMetaType<AlgoSet>("AlgoSet");
}

void AiWebsocket::conn(const QString& ws)
{
    /*
    @ Kind   : Change
    @ Author : Wei.Liu
    @ Date   : 2022-11-24
    @ Brief  : 修改websocket连接地址
    */
    QString ws_url = ws + "/?user=admin";
    m_uri = ws_url.toStdString();

    websocketpp::lib::error_code ec;
    WsClient::connection_ptr con = m_wsClient.get_connection(m_uri, ec);
    if(ec)
    {
        return;
    }

    m_hdl = con->get_handle();
    m_wsClient.connect(con);
}

void AiWebsocket::disconn()
{
    if(m_connected)
    {
        m_wsClient.close(m_hdl, websocketpp::close::status::normal, "");

        m_connected = false;
        emit sigConnectState(false);
    }
}

void AiWebsocket::onTextMessageReceived(const QString& message)
{
    qDebug() << "received:" << message;

    QJsonParseError receivedJsonParseError;
    QJsonDocument receivedJsonDoc = QJsonDocument::fromJson(message.toStdString().data(), &receivedJsonParseError);
    if(!receivedJsonDoc.isEmpty() && (receivedJsonParseError.error == QJsonParseError::NoError))
    {
        if(receivedJsonDoc.isObject())
        {
            QJsonObject receivedJsonObj = receivedJsonDoc.object();
            if(receivedJsonObj.contains("type"))
            {
                QString type = receivedJsonObj.value("type").toString();
                if(type == "response")
                {
                    if(receivedJsonObj.contains("method") && receivedJsonObj.contains("body"))
                    {
                        QString method = receivedJsonObj.value("method").toString();
                        QJsonObject bodyJsonObj = receivedJsonObj.value("body").toObject();

                        if(method == "GetDeviceInfo")
                        {
                            emit sigGetDeviceInfo(bodyJsonObj);

                        }
                        else if(method == "GetDeviceRunInfo")
                        {
                            emit sigGetDeviceRunInfo(bodyJsonObj);
                        }
                        else if(method == "GetNetworkParam")
                        {
                            emit sigGetNetworkParam(bodyJsonObj);
                        }
                        else if(method == "GetAlgoParam")
                        {
                            emit sigGetAlgoParam(bodyJsonObj);
                        }
                        else if(method == "GetZoneParam")
                        {
                            emit sigGetZoneParam(bodyJsonObj);
                        }
                        else if(method == "GetAlgoInfo")
                        {
                            emit sigGetAlgoInfo(bodyJsonObj);
                        }

                        else if(method == "GetExternalParam")
                        {
                            emit sigGetExternalParam(bodyJsonObj);
                        }
                        else if(method == "GetAlarmOut")
                        {
                            emit sigGetAlarmOut(bodyJsonObj);
                        }
                        else if(method == "GetAlgoPeopleCountParam")
                        {
                            emit sigGetAlgoPeopleCountParam(bodyJsonObj);
                        }
                        else if(method == "GetDetPeopleParam")
                        {
                            handleDetPeopleParam(bodyJsonObj);
                        }
                        else if(method == "GetCameraInfo")
                        {
                            handleCameraInfo(bodyJsonObj);
                        }
                        else if(method == "GetCameraParam")
                        {
                            handleCameraParam(bodyJsonObj);
                        }
                        else if(method == "GetDetBeltParam")
                        {
                            handleDetBeltParam(bodyJsonObj);
                        }
                        else if(method == "GetCoalSpdParam")
                        {
                            handleCoalSpdParam(bodyJsonObj);
                        }
                        else if(method == "GetOfflineSnapParam")
                        {
                            handleOfflineSnapParam(bodyJsonObj);
                        }
                    }
                    else if(receivedJsonObj.contains("method"))
                    {
                        QString method = receivedJsonObj.value("method").toString();

                        if(method == "SetNetworkParam")
                        {
                            emit sigSetNetworkParamSuccess();
                        }
                        else if(method == "SetCameraParam")
                        {
                            emit sigSetCameraParamSuccess();
                        }
                        else if(method == "SyncDateTime")
                        {
                            emit sigSyncDateTimeSuccess();
                        }
                        if(method == "SetRealTimeImage")
                        {

                        }
                    }
                }
                else if(type == "notice")
                {
                    if(receivedJsonObj.contains("method") && receivedJsonObj.contains("body"))
                    {
                        QString method = receivedJsonObj.value("method").toString();
                        QJsonObject bodyJsonObj = receivedJsonObj.value("body").toObject();

                        if(method == "AppUpgrade")
                        {
                            handleAppUpgrade(bodyJsonObj);
                        }
                        else if(method == "CoalSpdValue")
                        {
                            handleCoalSpdValue(bodyJsonObj);
                        }
                    }
                }
                else if(type == "request")
                {
                }
            }
        }
    }
}

void AiWebsocket::onBinaryMessageReceived(const QByteArray& message)
{
    // header

    WsBinHeader header;
    int headerLen = sizeof(WsBinHeader);
    memcpy(&header, message.data(), headerLen);
//    if(header.chn != m_chn)
//    {
//        return;
//    }
    // 接收图片 message = header + image + alarm_extern
    if(header.tag == WSBIN_TYPE_IMAGE && message.length() == (headerLen + header.totalSize))
    {
        // image
        QByteArray imageArr;
        // alarm_extern 为0xa5时，包含拓展报警信息
        int alarmExternLen = 8 * 1024;

        // 报警图片获取
        int imageLen = message.length() - headerLen - alarmExternLen;
        imageArr.resize(imageLen);
        memcpy(imageArr.data(), message.data() + headerLen, imageLen);

        // 拓展信息
        QByteArray alarmExternArr;
        alarmExternArr.resize(alarmExternLen);
        memcpy(alarmExternArr.data(), message.data() + headerLen + imageLen, alarmExternLen);
        QJsonParseError jsonParseError;
        QJsonDocument jsonDoc = QJsonDocument::fromJson(alarmExternArr.data(), &jsonParseError);

        if(!jsonDoc.isEmpty() && (jsonParseError.error == QJsonParseError::NoError))
        {
            if(jsonDoc.isObject())
            {
                AlarmExtern alarmExtern = handleAlarmExtern(jsonDoc.object());
                emit sigAlarmImage(header, imageArr, alarmExtern);
            }
        }
    }

    // 接收语音文件 message = header + voice
    if(header.tag == WSBIN_TYPE_VOICE && message.length() == (headerLen + header.totalSize))
    {
        // voice
        QByteArray voiceArr;

        // 语音文件获取
        int voiceLen = message.length() - headerLen;
        voiceArr.resize(voiceLen);
        memcpy(voiceArr.data(), message.data() + headerLen, voiceLen);

        emit sigVoiceTalkOut(header, voiceArr);
    }
}

void AiWebsocket::handleDeviceInfo(QJsonObject obj)
{
    DeviceInfo deviceInfo;
    deviceInfo.hw = obj.value("hw").toString();
    deviceInfo.sw = obj.value("sw").toString();
    deviceInfo.algover = obj.value("algoVer").toString();
    deviceInfo.modVer = obj.value("modVer").toString();
    deviceInfo.fwVer = obj.value("fwVer").toString();
    deviceInfo.devType = obj.value("devType").toString();
    deviceInfo.sn = obj.value("sn").toString();
    deviceInfo.devId = obj.value("devId").toString();
    QJsonArray chnInfo = obj["chnInfo"].toArray();
    deviceInfo.cntchn = chnInfo.size();
    for(int i = 0; i < deviceInfo.cntchn; i++)
    {
        deviceInfo.chnInfo[i].chnid = chnInfo.at(i).toObject().value("chnId").toString();
        deviceInfo.chnInfo[i].eventType = chnInfo.at(i).toObject().value("eventType").toString();
        deviceInfo.chnInfo[i].alarmType = chnInfo.at(i).toObject().value("alarmType").toString();
        deviceInfo.chnInfo[i].ipcIp = chnInfo.at(i).toObject().value("ipcIp").toString();
        deviceInfo.chnInfo[i].ipcUser = chnInfo.at(i).toObject().value("ipcUser").toString();
        deviceInfo.chnInfo[i].ipcPassword = chnInfo.at(i).toObject().value("ipcPassword").toString();
        deviceInfo.chnInfo[i].streamUrl = chnInfo.at(i).toObject().value("streamUrl").toString();
        deviceInfo.chnInfo[i].enable = chnInfo.at(i).toObject().value("enable").toInt();
        deviceInfo.chnInfo[i].online = chnInfo.at(i).toObject().value("online").toInt();
    }

    emit sigDeviceInfo(deviceInfo);
}

void AiWebsocket::handleDeviceRunInfo(QJsonObject obj)
{
    DeviceRunInfo deviceRunInfo;
    deviceRunInfo.dayTime = obj.value("dayTime").toString();
    deviceRunInfo.cpuUsage = obj.value("cpuUsage").toInt();
    deviceRunInfo.ramUsage = obj.value("ramUsage").toInt();
    deviceRunInfo.diskUsage = obj.value("diskUsage").toInt();
    deviceRunInfo.temp = obj.value("temp").toDouble();

    emit sigDeviceRunInfo(deviceRunInfo);
}

void AiWebsocket::handleNetworkParam(QJsonObject obj)
{
//    NetworkParam networkParam;
//    networkParam.ip1 = obj.value("ip1").toString();
//    networkParam.ip2 = obj.value("ip2").toString();
//    networkParam.netmask = obj.value("netmask").toString();
//    networkParam.gateway = obj.value("gateway").toString();
//    networkParam.dns = obj.value("dns").toString();
//    networkParam.mac = obj.value("mac").toString();
//    emit sigNetworkParam(networkParam);
}

void AiWebsocket::handleAlgoParam(QJsonObject obj)
{
    AlgoSet algo;
    algo.enable = obj.value("enable").toInt();
    algo.thresh = obj.value("thresh").toString().toFloat();
    algo.url = obj.value("url").toString();
    algo.eventType = obj.value("eventType").toString();
    algo.alarmType = obj.value("alarmType").toString();
    algo.alarmGrade = obj.value("alarmGrade").toInt();
    algo.ipcIp = obj.value("ipcIp").toString();
    algo.ipcUser = obj.value("ipcIp").toString();
    algo.ipcPassword = obj.value("ipcPassword").toString();
    emit sigAlgoParam(algo);
}

AlarmExtern AiWebsocket::handleAlarmExtern(QJsonObject obj)
{
    AlarmExtern alarmExtern;
    alarmExtern.chnId = obj.value("chnId").toString();
    alarmExtern.startTime = obj.value("start_time").toString();
    alarmExtern.stopTime = obj.value("stop_time").toString();
    alarmExtern.alarmType = obj.value("alarmType").toInt();
    alarmExtern.alarmImageId = obj.value("alarmImageId").toString();
    alarmExtern.desc = obj.value("desc").toString();
    alarmExtern.alarmGrade = obj.value("alarmGrade").toInt();

    QJsonArray jsonArr = obj.value("boxes").toArray();
    QJsonDocument jsonDoc = QJsonDocument(jsonArr);
    alarmExtern.boxes = QString(jsonDoc.toJson());

    alarmExtern.steps = obj.value("steps").toInt();

    jsonArr = obj.value("zones").toArray();
    jsonDoc = QJsonDocument(jsonArr);
    alarmExtern.zones = QString(jsonDoc.toJson());

    jsonArr = obj.value("lines").toArray();
    jsonDoc = QJsonDocument(jsonArr);
    alarmExtern.lines = QString(jsonDoc.toJson());
    return alarmExtern;
}

void AiWebsocket::handleDetPeopleParam(QJsonObject obj)
{
    DetPeopleParam detPeopleParam;
    detPeopleParam.enable = obj.value("enable").toInt();
    detPeopleParam.thresh = obj.value("thresh").toDouble();
    detPeopleParam.chn = obj.value("chn").toInt();
    detPeopleParam.source = obj.value("source").toInt();
    detPeopleParam.uuid = obj.value("uuid").toString();
    detPeopleParam.uri = obj.value("uri").toString();
    detPeopleParam.algoType = obj.value("algoType").toInt();
    detPeopleParam.alarmConfirmTime = obj.value("alarmConfirmTime").toInt();
    detPeopleParam.alarmLeaveTime = obj.value("alarmLeaveTime").toInt();
    detPeopleParam.peopleMax = obj.value("peopleMax").toInt();

    emit sigDetPeopleParam(detPeopleParam);
}

void AiWebsocket::handleCameraInfo(QJsonObject obj)
{
    CameraInfo cameraInfo;
    cameraInfo.fps = obj.value("fps").toInt();
    cameraInfo.seq = obj.value("seq").toInt();
    cameraInfo.width = obj.value("width").toInt();
    cameraInfo.height = obj.value("height").toInt();

    emit sigCameraInfo(cameraInfo);
}

void AiWebsocket::handleCameraParam(QJsonObject obj)
{
    CameraParam cameraParam;
    cameraParam.expose = obj.value("expose").toInt();
    cameraParam.gain = obj.value("gain").toInt();

    if(obj.value("ROI").isObject())
    {
        QJsonObject roiJsonObj = obj.value("ROI").toObject();
        cameraParam.roi.x = roiJsonObj.value("x").toInt();
        cameraParam.roi.y = roiJsonObj.value("y").toInt();
        cameraParam.roi.w = roiJsonObj.value("w").toInt();
        cameraParam.roi.h = roiJsonObj.value("h").toInt();
    }

    emit sigCameraParam(cameraParam);
}

void AiWebsocket::handleDetBeltParam(QJsonObject obj)
{
    DetBeltParam detBeltParam;
    detBeltParam.enable = obj.value("enable").toInt();
    detBeltParam.thresh = obj.value("thresh").toDouble();
    detBeltParam.alarmRefId = obj.value("AlarmRefId").toInt();
    detBeltParam.refBoxCnt = obj.value("RefBoxCnt").toInt();

    if(obj.value("RefBoxs").isArray())
    {
        QJsonArray refBoxsArr = obj.value("RefBoxs").toArray();
        for(int i = 0; i < refBoxsArr.count(); ++i)
        {
            if(refBoxsArr.at(i).isObject())
            {
                QJsonObject refBoxObj = refBoxsArr.at(i).toObject();
                RefBox refBox;
                refBox.name = refBoxObj.value("name").toString();
                refBox.grade = refBoxObj.value("grade").toInt();
                refBox.x = refBoxObj.value("x").toDouble();
                refBox.y = refBoxObj.value("y").toDouble();
                refBox.w = refBoxObj.value("w").toDouble();
                refBox.h = refBoxObj.value("h").toDouble();

                detBeltParam.refBoxs.push_back(refBox);
            }
        }
    }

    detBeltParam.zoneCnt = obj.value("ZoneCnt").toInt();

    if(obj.value("Zones").isArray())
    {
        QJsonArray zonesArr = obj.value("Zones").toArray();
        for(int i = 0; i < zonesArr.count(); ++i)
        {
            if(zonesArr.at(i).isArray())
            {
                QJsonArray zoneArr = zonesArr.at(i).toArray();
                Zone zone;

                for(int j = 0; j < zoneArr.count(); ++j)
                {
                    if(zoneArr.at(j).isObject())
                    {
                        QJsonObject zonePointObj = zoneArr.at(j).toObject();
                        ZonePoint zonePoint;

                        zonePoint.id = zonePointObj.value("id").toInt();
                        zonePoint.x = zonePointObj.value("x").toDouble();
                        zonePoint.y = zonePointObj.value("y").toDouble();
                        zone.zonePoints.push_back(zonePoint);
                    }
                }

                detBeltParam.zones.push_back(zone);
            }
        }
    }

    if(obj.value("AlarmOut").isArray())
    {
        QJsonArray alarmOutArr = obj.value("AlarmOut").toArray();
        for(int i = 0; i < alarmOutArr.count(); ++i)
        {
            if(alarmOutArr.at(i).isObject())
            {
                QJsonObject alarmOutObj = alarmOutArr.at(i).toObject();
                AlarmOut alarmOut;

                alarmOut.alarmId = alarmOutObj.value("alarmId").toInt();
                alarmOut.enable = alarmOutObj.value("enable").toInt();
                alarmOut.io = alarmOutObj.value("IO").toInt();

                detBeltParam.alarmOut.push_back(alarmOut);
            }
        }
    }

    detBeltParam.lineCnt = obj.value("LineCnt").toInt();

    if(obj.value("Lines").isArray())
    {
        QJsonArray linesArr = obj.value("Lines").toArray();
        for(int i = 0; i < linesArr.count(); ++i)
        {
            if(linesArr.at(i).isArray())
            {
                QJsonArray lineArr = linesArr.at(i).toArray();
                if(lineArr.count() == 2)
                {
                    QLineF lineF;

                    if(lineArr.at(0).isObject())
                    {
                        QJsonObject p1Obj = lineArr.at(0).toObject();
                        QPointF p1;
                        p1.setX(p1Obj.value("x").toDouble());
                        p1.setY(p1Obj.value("y").toDouble());

                        lineF.setP1(p1);
                    }
                    if(lineArr.at(1).isObject())
                    {
                        QJsonObject p2Obj = lineArr.at(1).toObject();
                        QPointF p2;
                        p2.setX(p2Obj.value("x").toDouble());
                        p2.setY(p2Obj.value("y").toDouble());

                        lineF.setP2(p2);
                    }

                    detBeltParam.lines.append(lineF);
                }
            }
        }
    }

    emit sigDetBeltParam(detBeltParam);
}

void AiWebsocket::handleAppUpgrade(QJsonObject obj)
{
    AppUpgrade appUpgrade;

    appUpgrade.step = obj.value("step").toInt();
    appUpgrade.pct = obj.value("pct").toInt();

    emit sigAppUpgrade(appUpgrade);
}

void AiWebsocket::handleCoalSpdParam(QJsonObject obj)
{
    CoalSpdParam coalSpdParam;
    coalSpdParam.thresh_auto = obj.value("threshAuto").toInt();
    coalSpdParam.thresh = obj.value("thresh").toInt();
    coalSpdParam.func = obj.value("func").toInt();
    coalSpdParam.factor = obj.value("factor").toDouble();

    emit sigCoalSpdParam(coalSpdParam);
}

void AiWebsocket::handleCoalSpdValue(QJsonObject obj)
{
    CoalSpdValue coalSpdValue;
    coalSpdValue.coalVolume = obj.value("coalVolume").toDouble();
    //coalSpdValue.coalGrade = obj.value("coal_grade").toInt();
    coalSpdValue.beltTs = obj.value("beltTs").toInt();
    coalSpdValue.beltVolume = obj.value("beltVolume").toDouble();
    coalSpdValue.beltSpd = obj.value("beltSpd").toDouble();

    emit sigCoalSpdValue(coalSpdValue);
}

void AiWebsocket::handleOfflineSnapParam(QJsonObject obj)
{
    OfflineSnapParam offlineSnapParam;
    offlineSnapParam.enable = obj.value("enable").toInt();
    offlineSnapParam.path = obj.value("path").toString();
    offlineSnapParam.interval = obj.value("interval").toDouble();
    offlineSnapParam.maxCapSize = obj.value("maxCapSize").toInt();
    offlineSnapParam.dirSize = obj.value("dirSize").toInt();

    emit sigOfflineSnapParam(offlineSnapParam);
}

void AiWebsocket::init()
{
    m_wsClient.set_access_channels(websocketpp::log::alevel::all);
    m_wsClient.clear_access_channels(websocketpp::log::alevel::frame_payload);
    m_wsClient.clear_access_channels(websocketpp::log::alevel::frame_header);

    m_wsClient.init_asio();
    m_wsClient.set_open_handler(websocketpp::lib::bind(&AiWebsocket::on_open, this, ::_1));
    m_wsClient.set_fail_handler(websocketpp::lib::bind(&AiWebsocket::on_fail, this, ::_1));
    m_wsClient.set_close_handler(websocketpp::lib::bind(&AiWebsocket::on_close, this, ::_1));
    m_wsClient.set_message_handler(websocketpp::lib::bind(&AiWebsocket::on_message, this, ::_1, ::_2));

    m_wsClient.start_perpetual();
    m_wsThread = websocketpp::lib::make_shared<websocketpp::lib::thread>(&WsClient::run, &m_wsClient);
}

void AiWebsocket::on_open(websocketpp::connection_hdl hdl)
{
    Q_UNUSED(hdl);

    websocketpp::lib::error_code ec;
    if(ec)
    {
        return;
    }

    m_connected = true;
    emit sigConnectState(true);
}

void AiWebsocket::on_fail(websocketpp::connection_hdl hdl)
{
    Q_UNUSED(hdl);

    m_connected = false;
    emit sigConnectFail();
}

void AiWebsocket::on_close(websocketpp::connection_hdl hdl)
{
    Q_UNUSED(hdl);
    if(m_connected)
    {
        m_connected = false;
        emit sigConnectState(false);
        emit sigReconnect();
    }
}

void AiWebsocket::on_message(websocketpp::connection_hdl hdl, message_ptr msg)
{
    Q_UNUSED(hdl);

    websocketpp::lib::error_code ec;
    if(ec)
    {
        qDebug() << "error:" << QString::fromStdString(ec.message());
        return;
    }

    switch(msg->get_opcode())
    {
        case websocketpp::frame::opcode::text:
            {
                Singleton::Instance()->onTextMessageReceived(QString::fromStdString(msg->get_payload()));
                //this->onTextMessageReceived(QString::fromStdString(msg->get_payload()));
                break;
            };
        case websocketpp::frame::opcode::binary:
            {
                int length = msg->get_payload().length();
                QByteArray binaryArr;
                binaryArr.resize(length);
                memcpy(binaryArr.data(), msg->get_payload().data(), length);
                Singleton::Instance()->onBinaryMessageReceived(binaryArr);
                //this->onBinaryMessageReceived(binaryArr);
                break;
            };
        default:
            break;
    }
}

bool AiWebsocket::connected() const
{
    return m_connected;
}

void AiWebsocket::setConnected(bool connected)
{
    m_connected = connected;

    emit sigConnectState(connected);
}

void AiWebsocket::terminate()
{
    m_wsClient.stop_perpetual();
#if 0
    // join 等待线程完成函数，主线程需等待子线程运行结束后才可以结束
    m_wsThread->join();
#else
    // detach 分离线程函数，主线程无需等待子线程运行结束
    m_wsThread->detach();
#endif
}

void AiWebsocket::setChn(int chn)
{
    m_chn = chn;
}

void AiWebsocket::sendRequest(const QString& method, int seq, QJsonObject body)
{
    QJsonObject requestJsonObj;
    requestJsonObj.insert("type", "request");
    requestJsonObj.insert("method", method);
    requestJsonObj.insert("seq", seq);
    requestJsonObj.insert("body", QJsonValue(body));

    QJsonDocument requestJsonDoc;
    requestJsonDoc.setObject(requestJsonObj);

    QString request = QString(requestJsonDoc.toJson(QJsonDocument::Compact));
    qDebug() << "request:" << request;
    if(m_connected)
    {
        m_wsClient.send(m_hdl, request.toStdString(), websocketpp::frame::opcode::text);
    }
}

void AiWebsocket::sendBinaryMessage(const QByteArray& message)
{

    if(m_connected)
    {
        m_wsClient.send(m_hdl, message.toStdString(), websocketpp::frame::opcode::binary);
    }
}
